package com.zzyl.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.text.CharSequenceUtil;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.zzyl.base.PageResponse;
import com.zzyl.constant.CacheConstants;
import com.zzyl.dto.AlertRuleDto;
import com.zzyl.entity.AlertData;
import com.zzyl.entity.AlertRule;
import com.zzyl.mapper.AlertDataMapper;
import com.zzyl.mapper.AlertRuleMapper;
import com.zzyl.mapper.DeviceMapper;
import com.zzyl.mapper.UserRoleMapper;
import com.zzyl.service.AlertRuleService;
import com.zzyl.vo.AlertRuleVo;
import com.zzyl.vo.DeviceDataVo;
import lombok.extern.slf4j.Slf4j;
import org.mockito.internal.matchers.CompareTo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author sjqn
 */
@Slf4j
@Service
public class AlertRuleServiceImpl implements AlertRuleService {

    @Autowired
    private AlertRuleMapper alertRuleMapper;

    /**
     * 新增报警规则
     *
     * @param alertRuleDto 报警规则请求模型
     */
    @Override
    public void createRule(AlertRuleDto alertRuleDto) {
        AlertRule alertRule = BeanUtil.toBean(alertRuleDto, AlertRule.class);
        alertRuleMapper.insert(alertRule);
    }

    /**
     * 分页条件查询
     *
     * @param pageNum       页码
     * @param pageSize      页面大小
     * @param alertRuleName 报警规则名称
     * @param productKey    产品key
     * @param functionName  功能名称
     * @return 分页结果
     */
    @Override
    public PageResponse<AlertRuleVo> getAlertRulePage(Integer pageNum, Integer pageSize, String alertRuleName, String productKey, String functionName) {
        PageHelper.startPage(pageNum, pageSize);
        Page<AlertRuleVo> page = alertRuleMapper.page(alertRuleName, productKey, functionName);
        //添加提示
        page.getResult().forEach(alertRuleVo -> {
            String alertReason = CharSequenceUtil.format("{}{}{},持续{}个周期就报警", alertRuleVo.getFunctionName(), alertRuleVo.getOperator(), alertRuleVo.getValue(), alertRuleVo.getDuration());
            alertRuleVo.setRules(alertReason);
        });

        return PageResponse.of(page, AlertRuleVo.class);
    }

    /**
     * 删除报警
     *
     * @param id 报警规则id
     */
    @Override
    public void deleteAlertRule(Long id) {
        alertRuleMapper.deleteByPrimaryKey(id);
    }

    /**
     * 修改报警
     *
     * @param id           报警规则id
     * @param alertRuleDto 报警规则更新请求模型
     */
    @Override
    public void updateAlertRule(Long id, AlertRuleDto alertRuleDto) {
        AlertRule alertRule = BeanUtil.toBean(alertRuleDto, AlertRule.class);
        alertRuleMapper.updateByPrimaryKeySelective(alertRule);
    }

    /**
     * 查询报警信息
     *
     * @param id 报警规则id
     * @return 报警信息
     */
    @Override
    public AlertRuleVo readAlertRule(Long id) {
        AlertRule alertRule = alertRuleMapper.selectByPrimaryKey(id);
        return BeanUtil.toBean(alertRule, AlertRuleVo.class);
    }

    /**
     * 启用或禁用
     *
     * @param id     报警id
     * @param status 报警规则状态
     */
    @Override
    public void enableOrDisable(Long id, Integer status) {
        alertRuleMapper.updateStatus(id, status);
    }

    /**
     * 过滤数据
     *
     * @param deviceDataVo
     */
    @Override
    public void alertFilter(DeviceDataVo deviceDataVo) {
        //判断数据是否是一分钟之内的数据
        long between = LocalDateTimeUtil.between(deviceDataVo.getAlarmTime(), LocalDateTime.now(), ChronoUnit.SECONDS);
        //数据过期
        if (between > 60) {
            return;
        }

        //查询该产品下物模型下的所有的规则
        List<AlertRule> allAlertRules = alertRuleMapper.selectByFunctionId(deviceDataVo.getFunctionId(), "-1", deviceDataVo.getProductKey());

        //查询该产品物模型下对应设备规则
        List<AlertRule> deviceAlertRules = alertRuleMapper.selectByFunctionId(deviceDataVo.getFunctionId(), deviceDataVo.getIotId(), deviceDataVo.getProductKey());

        //所有规则
        List<AlertRule> alertRules = new ArrayList<>();

        if (allAlertRules != null) {
            alertRules.addAll(allAlertRules);
        }

        if (deviceAlertRules != null) {
            alertRules.addAll(deviceAlertRules);
        }

        //如果没有对应规则
        if (alertRules.isEmpty()) {
            return;
        }

        //遍历每一条规则,进行下一步规则校验
        alertRules.forEach(alertRule -> {
            deviceDateAlarmHandler(deviceDataVo, alertRule);
        });

    }

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Autowired
    private DeviceMapper deviceMapper;

    @Autowired
    private UserRoleMapper userRoleMapper;

    @Value("${zzyl.alert.deviceMaintainerRole}")
    private String deviceMaintainerRole;

    @Value("${zzyl.alert.managerRole}")
    private String managerRole;


    /**
     * 过滤数据是否触发报警规则
     *
     * @param deviceDataVo
     * @param rule
     */
    private void deviceDateAlarmHandler(DeviceDataVo deviceDataVo, AlertRule rule) {
        //判断数据上报时间是否在生效时间内
        String[] split = rule.getAlertEffectivePeriod().split("~");
        LocalTime startTime = LocalTime.parse(split[0]);
        LocalTime endTime = LocalTime.parse(split[1]);
        LocalTime alarmTime = deviceDataVo.getAlarmTime().toLocalTime();
        //数据上报时间不在生效时间内直接return
        if (startTime.isAfter(alarmTime) || endTime.isBefore(alarmTime)) {
            return;
        }

        //检验数据是否达到阈值
        //比较设备数据中的值与阈值
        String dataValue = deviceDataVo.getDataValue();
        Float value = rule.getValue();
        int i = value.compareTo(Float.parseFloat(dataValue));
        //如果i = 1 则小于阈值， i = -1 则大于阈值
        //校验是否符合报警条件，如果不符合，删除此前已累计的报警数，直接return
        if (!((rule.getOperator().equals(">=") && i <= 0) || (rule.getOperator().equals("<") && i > 0))) {
            redisTemplate.delete(CacheConstants.IOT_ALARM_COUNT + deviceDataVo.getIotId() + rule.getId());
            return;
        }

        //达到阈值，判断沉默周期
        //设置沉默周期储存在redis中的key
        String silent = rule.getProductKey() + rule.getFunctionId() + rule.getId();

        String s = redisTemplate.opsForValue().get(CacheConstants.IOT_SILENT_DURATION + silent);

        //正处于沉默周期，直接结束
        if (s != null) {
            return;
        }

        //查询redis中的已报警数据并计算
        String alarmCount = redisTemplate.opsForValue().get(CacheConstants.IOT_ALARM_COUNT + deviceDataVo.getIotId() + rule.getId());

        int count = alarmCount == null ? 1 : Integer.parseInt(alarmCount) + 1;

        //判断已报警次数是否等于持续周期
        //不等于则将报警次数存入redis中
        if (count != rule.getDuration()) {
            redisTemplate.opsForValue().set(CacheConstants.IOT_ALARM_COUNT + deviceDataVo.getIotId() + rule.getId(), String.valueOf(count));
            return;
        }

        //等于则删除redis报警数
        redisTemplate.delete(CacheConstants.IOT_ALARM_COUNT + deviceDataVo.getIotId() + rule.getId());

        //添加redis沉默周期，并设置过期时间
        redisTemplate.opsForValue().set(CacheConstants.IOT_SILENT_DURATION + silent, "1", rule.getAlertSilentPeriod(), TimeUnit.MINUTES);

        //触发报警
        //封装需要发送消息的接收者
        List<Long> userIds = new ArrayList<>();
        //是否是老人数据
        if (rule.getAlertDataType() == 0) {
            if (deviceDataVo.getLocationType() == 0) {
                List<Long> nursingIds = deviceMapper.selectDeviceWithElderNursingIdByIotId(deviceDataVo.getIotId());
                userIds.addAll(nursingIds);
            } else {
                List<Long> nursingIds = deviceMapper.selectDeviceWithElderNursingIdByBed(deviceDataVo.getIotId());
                userIds.addAll(nursingIds);
            }
        } else {
            List<Long> deviceMaintainerRole = userRoleMapper.selectDeviceMaintainerRoleByRoleName("修理工");
            userIds.addAll(deviceMaintainerRole);
        }

        List<Long> adminIds = userRoleMapper.selectDeviceMaintainerRoleByRoleName("超级管理员");

        userIds.addAll(adminIds);

        List<Long> collect = userIds.stream().distinct().collect(Collectors.toList());

        insertAlertData(collect,rule,deviceDataVo);


    }

    @Autowired
    private AlertDataMapper alertDataMapper;

    /**
     * 保存报警数据
     *
     * @param allIds
     * @param rule
     * @param deviceDataVo
     */
    private void insertAlertData(List<Long> allIds, AlertRule rule, DeviceDataVo deviceDataVo) {
        // 先拷贝属性
        AlertData data = BeanUtil.toBean(rule, AlertData.class);
        // 创建AlertData对象，将其他数据，封装到该对象中。
        data.setType(rule.getAlertDataType());
        data.setAlertRuleId(rule.id);
        data.setStatus(0); // 0 待处理
        String alertReason = CharSequenceUtil.format("{}{}{},持续{}个周期就报警", rule.getFunctionName(),
                rule.getOperator(), rule.getValue(), rule.getDuration());
        data.setAlertReason(alertReason);
        data.setAccessLocation(deviceDataVo.getAccessLocation());
        data.setLocationType(deviceDataVo.getLocationType());
        data.setPhysicalLocationType(deviceDataVo.getPhysicalLocationType());
        data.setDataValue(deviceDataVo.getDataValue());
        // 遍历ids集合，将数据，封装到AlertData对象中
        List<AlertData> collect = allIds.stream().map(id -> {
            AlertData alertData = BeanUtil.toBean(data, AlertData.class);
            alertData.setUserId(id); // 封装用户(护理员、维修工、超集管理员)id
            return alertData;
        }).collect(Collectors.toList());

        // 将集合中的数据，批量插入到报警数据表中
        alertDataMapper.batchInsert(collect);

    }
}
